package com.baijiaoxi.utils.qcloud;

import com.baijiaoxi.common.model.base.CommonError;
import com.baijiaoxi.common.model.base.CoreException;
import com.google.gson.Gson;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.http.HttpMethodName;
import com.qcloud.cos.model.*;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import static com.baijiaoxi.utils.qcloud.ContentTypeUtil.getContentTypeByFileName;

/**
 * 腾讯云对象存储工具类
 */
@Slf4j
public class QcloudHelper implements OSHelper {
    private String regionName;
    private String bucketName;
    private COSClient cosClient;
    private boolean shutdownAfterUseStat = true;

    public QcloudHelper(COSClient cosClient, String regionName, String bucketName) {
        this.cosClient = cosClient;
        this.regionName = regionName;
        this.bucketName = bucketName;
    }

    @Override
    public boolean isShutdownAfterUseStat() {
        return shutdownAfterUseStat;
    }

    @Override
    public OSHelper setShutdownAfterUseStat(boolean shutdownAfterUseStat) {
        this.shutdownAfterUseStat = shutdownAfterUseStat;
        return this;
    }

    @Override
    public void close() {
        try {
            cosClient.shutdown();
        } catch (Exception e) {
            log.error("ossClient关闭异常", e);
        }
    }


    private void ifShutdown(boolean shutdownAfterUseStat) {
        if (shutdownAfterUseStat)
            close();
    }

    @Override
    public String getSignUploadUrl(String fileKey, String contentType, boolean shutdownAfterUseStat) {
        return "";
    }

    /**
     * 上传
     *
     * @param key
     * @param url
     * @param shutdownAfterUseStat
     */
    @Override
    public void upload(String key, String originFilename, String url, boolean shutdownAfterUseStat, boolean addMeta) {
        throw CoreException.create(CommonError.notSupport, "腾讯云暂不支持网络链接上传文件");
    }

    /**
     * 上传
     * 流式
     *
     * @param key
     * @param inputStream
     * @param shutdownAfterUseStat
     */
    @Override
    public void upload(String key, String originFilename, InputStream inputStream, boolean shutdownAfterUseStat, boolean addMeta) {
        ObjectMetadata metadata = createMeta(key, originFilename, addMeta);
        // bucket的命名规则为{name}-{appid} ，此处填写的存储桶名称必须为此格式
        // 指定要上传到 COS 上对象键
        // 对象键（Key）是对象在存储桶中的唯一标识。例如，在对象的访问域名 `bucket1-1250000000.cos.ap-guangzhou.myqcloud.com/doc1/pic1.jpg` 中，对象键为 doc1/pic1.jpg, 详情参考 [对象键](https://cloud.tencent.com/document/product/436/13324)
        // 设置存储类型, 默认是标准(Standard), 低频(standard_ia)
        try {
            PutObjectResult putObjectResult = cosClient.putObject(bucketName, key, inputStream, metadata);
            inputStream.close();
            // putobjectResult会返回文件的etag
            // String etag = putObjectResult.getETag();
        } catch (Exception e) {
            throw CoreException.create(CommonError.cosUploadError, e, bucketName, key);
        } finally {
            ifShutdown(shutdownAfterUseStat);
        }
    }


    /**
     * 上传
     *
     * @param key
     * @param localFile
     * @param shutdownAfterUseStat
     */
    @Override
    public void upload(String key, String originFilename, File localFile, boolean shutdownAfterUseStat, boolean addMeta) {
        ObjectMetadata metadata = createMeta(key, originFilename, addMeta);
        // bucket的命名规则为{name}-{appid} ，此处填写的存储桶名称必须为此格式
        // 指定要上传到 COS 上对象键
        // 对象键（Key）是对象在存储桶中的唯一标识。例如，在对象的访问域名 `bucket1-1250000000.cos.ap-guangzhou.myqcloud.com/doc1/pic1.jpg` 中，对象键为 doc1/pic1.jpg, 详情参考 [对象键](https://cloud.tencent.com/document/product/436/13324)
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, localFile);
        putObjectRequest.withMetadata(metadata);
        // 设置存储类型, 默认是标准(Standard), 低频(standard_ia)
        putObjectRequest.setStorageClass(StorageClass.Standard_IA);
        try {
            PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest);
            // putobjectResult会返回文件的etag
            // String etag = putObjectResult.getETag();
        } catch (Exception e) {
            log.error("上传腾讯云失败：", e);
            throw CoreException.create(CommonError.cosUploadError, e, localFile.getAbsolutePath());
        } finally {
            ifShutdown(shutdownAfterUseStat);
        }
    }

    /**
     * 上传
     *
     * @param key
     * @param localFile
     * @param shutdownAfterUseStat
     */
    @Override
    public void uploadWithPart(String key, String originFilename, File localFile, boolean shutdownAfterUseStat, boolean addMeta) {
        boolean uploadWithPartRes = false;
        if (localFile.length() > partLimit) {
            List<String> fullPartFileNameList = cutFile(localFile, partSize);
            //尝试切割文件
            if (!fullPartFileNameList.isEmpty()) {
                log.info("开始分片上传：" + key + "" + new Gson().toJson(fullPartFileNameList));
                //分快上传
                List<PartETag> partETags = new ArrayList<PartETag>();

                ObjectMetadata metadata = createMeta(key, originFilename, addMeta);
                InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(bucketName, key, metadata);
                InitiateMultipartUploadResult initResponse = cosClient.initiateMultipartUpload(initRequest);
                String uploadId = initResponse.getUploadId();
                try {
                    for (int i = 0; i < fullPartFileNameList.size(); i++) {
                        String fullPartFileName = fullPartFileNameList.get(i);
                        int partNumber = i + 1;
                        log.debug("开始分片上传：{}{}", key, partNumber);
                        File partFile = new File(fullPartFileName);
                        FileInputStream fileInputStream = new FileInputStream(fullPartFileName);
                        UploadPartRequest uploadRequest = new UploadPartRequest().withBucketName(bucketName).
                                withUploadId(uploadId).withKey(key).withPartNumber(partNumber).
                                withInputStream(fileInputStream).withPartSize(partFile.length());
                        UploadPartResult uploadPartResult = cosClient.uploadPart(uploadRequest);
                        fileInputStream.close();
                        // 获取分块的 Etag
                        String etag = uploadPartResult.getETag();
                        // 获取分块的 CRC64
                        partETags.add(new PartETag(partNumber, etag));
                        partFile.delete();
                        log.debug("完成分片上传：{}{}", key, partNumber);
                    }
                    log.debug("准备结束分片上传：{}", key);
                    CompleteMultipartUploadRequest compRequest = new CompleteMultipartUploadRequest(bucketName, key, uploadId, partETags);
                    CompleteMultipartUploadResult result = cosClient.completeMultipartUpload(compRequest);
                    log.debug("完成结束分片上传：{}", key);
                    uploadWithPartRes = true;
                } catch (Exception e) {
                    AbortMultipartUploadRequest abortMultipartUploadRequest = new AbortMultipartUploadRequest(bucketName, key, uploadId);
                    cosClient.abortMultipartUpload(abortMultipartUploadRequest);
                    log.error("分块上传腾讯云失败：", e);
                } finally {
                    ifShutdown(shutdownAfterUseStat);
                }
            }
        }
        if (!uploadWithPartRes) {
            log.error("分块上传腾讯云失败,尝试整体上传{}", key);
            upload(key, originFilename, localFile, shutdownAfterUseStat, addMeta);
        }

    }

    /**
     * 分割文件
     * 每个10m
     * 成功返回切割的list
     * 失败返回空list
     *
     * @param localFile
     * @return
     */
    public static List<String> cutFile(File localFile, long partSize) {
        String filePath = localFile.getAbsolutePath();
        List<String> list = new ArrayList<>();
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(localFile);
            //创建规定大小的byte数组

            byte[] b = new byte[Long.valueOf(partSize).intValue()];
            int len;
            //name为以后的小文件命名做准备
            int index = 1;
            //遍历将大文件读入byte数组中，当byte数组读满后写入对应的小文件中
            while ((len = fis.read(b)) != -1) {
                String fullPartFileName = filePath + "_" + index;
                //分别找到原大文件的文件名和文件类型，为下面的小文件命名做准备
                FileOutputStream fos = new FileOutputStream(fullPartFileName);
                //将byte数组写入对应的小文件中
                fos.write(b, 0, len);
                //结束资源
                fos.close();
                index++;
                list.add(fullPartFileName);
            }
        } catch (Exception e) {
            log.error("分块上传腾讯云失败：文件分块失败", e);
        } finally {
            try {
                if (fis != null) {
                    //结束资源
                    fis.close();
                }
            } catch (IOException e) {
                log.error("分块上传腾讯云失败：文件分块关闭流失败", e);
            }
        }
        return list;
    }

    /**
     * 获取有过期时间的链接
     *
     * @param key
     * @param expirationDate
     * @param shutdownAfterUseStat
     * @return
     */
    @Override
    public String getUrl(String key, Date expirationDate, boolean shutdownAfterUseStat) {
        // 设置bucket名, bucket名需包含appid
        GeneratePresignedUrlRequest req =
                new GeneratePresignedUrlRequest(bucketName, key, HttpMethodName.GET);
        // 设置签名过期时间(可选), 过期时间不做限制，只需比当前时间大, 若未进行设置, 则默认使用ClientConfig中的签名过期时间(5分钟)
        // 这里设置签名在半个小时后过期
        req.setExpiration(expirationDate);
        URL url = cosClient.generatePresignedUrl(req);
        ifShutdown(shutdownAfterUseStat);
        return url.toString();
    }


    /**
     * 删除
     *
     * @param key
     * @param shutdownAfterUseStat
     */
    @Override
    public void del(String key, boolean shutdownAfterUseStat) {
        // bucket名需包含appid
        try {
            cosClient.deleteObject(bucketName, key);
        } catch (Exception e) {
            throw CoreException.create(CommonError.cosDelFileError, e, bucketName, key);
        } finally {
            ifShutdown(shutdownAfterUseStat);
        }
    }

    public String getStaticUrl(String fileKey) {
        return "https://" + bucketName + ".cos." + regionName + ".myqcloud.com/" + fileKey;
    }

    /**
     * 获取对象信息
     *
     * @param fileKey
     * @param shutdownAfterUseStat
     * @return
     */
    @Override
    public ObjectInfo getObjectInfo(String fileKey, boolean shutdownAfterUseStat) {
        return null;
    }

    /**
     * 获取对象信息
     *
     * @param fileKey
     * @return
     */
    @Override
    public ObjectInfo getObjectInfo(String fileKey) {
        return getObjectInfo(fileKey, true);
    }

    /**
     * 修改文件头
     *
     * @param fileKey
     * @param downloadName
     * @param cacheControl
     * @param shutdownAfterUseStat
     */
    @Override
    public void modifyContentType(String fileKey, String downloadName, String cacheControl, boolean shutdownAfterUseStat) {

    }

    @Override
    public void clearContentDisposition(String fileKey, boolean shutdownAfterUseStat) {

    }

    /**
     * 下载文件
     *
     * @param fileKey
     * @param fullLocalPath
     * @param shutdownAfterUseStat
     */
    @Override
    public void download(String fileKey, String fullLocalPath, boolean shutdownAfterUseStat) {

    }

    private ObjectMetadata createMeta(String fileKey, String originFilename, boolean addMeta) {
        ObjectMetadata meta = new ObjectMetadata();
        meta.setContentType(getContentTypeByFileName(fileKey));
        if (addMeta)
            meta.setContentDisposition(String.format("attachment;filename=\"%s\"", URLEncoder.encode(originFilename, StandardCharsets.UTF_8)));
        return meta;
    }
}
